﻿using MessagePack;
using xBei.Redis.Extension;

namespace xBei.Redis.Extensions.MsgPack {
    /// <summary>
    /// 扩展
    /// </summary>
    public static class RedisMsgPackHelper {
        #region 系列化
        /// <summary>
        /// 序列化
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static byte[] SerializeMsgPack<T>(this T obj) where T : class, new() {
            return MessagePackSerializer.Serialize(obj);
        }
        /// <summary>
        /// 序列化
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static async Task<byte[]> SerializeMsgPackAsync<T>(this Task<T> obj) where T : class, new() {
            return MessagePackSerializer.Serialize(await obj);
        }
        /// <summary>
        /// 反序列化
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="bytes">字节</param>
        /// <param name="throwErr">发生错误时是否抛出异常</param>
        /// <returns></returns>
        public static T? DeserializeMsgPack<T>(this byte[]? bytes, bool throwErr = false) where T : class, new() {
            if (bytes == null) return default;
            try {
                return MessagePackSerializer.Deserialize<T>(bytes);
            } catch (Exception) {
                if (throwErr) throw;
                return default;
            }
        }
        /// <summary>
        /// 反序列化
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="bytes">字节</param>
        /// <param name="throwErr">发生错误时是否抛出异常</param>
        /// <returns></returns>
        public static async Task<T?> DeserializeMsgPackAsync<T>(this Task<byte[]?> bytes, bool throwErr = false) where T : class, new() {
            if (await bytes == null) return default;
            try {
                return MessagePackSerializer.Deserialize<T>(await bytes);
            } catch (Exception) {
                if (throwErr) throw;
                return default;
            }
        }
        #endregion
        #region MessagePack
        /// <summary>
        /// 写入MessagePack，<see href="https://redis.io/commands/set/"/>
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="redisClient"></param>
        /// <param name="key"></param>
        /// <param name="messagePackObj"></param>
        /// <param name="seconds"></param>
        /// <returns></returns>
        public static async Task<RedisClient> SetMsgPackObjAsync<T>(this RedisClient redisClient, string key, T messagePackObj, int seconds = 315360000) where T : class, new() {
            return await redisClient.SetBytesAsync(key, SerializeMsgPack(messagePackObj), seconds);
        }
        /// <summary>
        /// 读取MessagePack，<see href="https://redis.io/commands/get/"/>
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="redisClient"></param>
        /// <param name="key"></param>
        /// <param name="throwErr">反系列化时发生错误是否抛出异常</param>
        /// <returns></returns>
        public static async Task<T?> GetMsgPackObjAsync<T>(this RedisClient redisClient, string key, bool throwErr = false) where T : class, new() {
            return await redisClient.GetBytesAsync(key).DeserializeMsgPackAsync<T>(throwErr);
        }
        #endregion
        #region 队列 Queue 先进先出
        /// <summary>
        /// 把MessagePack对象写入队列，<see href="https://redis.io/commands/rpush/"/>
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="redisClient"></param>
        /// <param name="key"></param>
        /// <param name="messagePackObj"></param>
        /// <param name="seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次入队没有指定超时时间，之后的入队操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次入队的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的入队操作来计算的。
        /// </param>
        /// <returns></returns>
        public static async Task<RedisClient> EnqueueAsync<T>(this RedisClient redisClient, string key, T messagePackObj, int seconds = 315360000) where T : class, new() {
            return await redisClient.EnqueueAsync(key, SerializeMsgPack(messagePackObj), seconds);
        }
        /// <summary>
        /// 出队一个MessagePack对象，<see href="https://redis.io/commands/lpop/"/>，没有队列，或者队列为空，返回 null
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="redisClient"></param>
        /// <param name="key"></param>
        /// <param name="throwErr">反系列化时发生错误是否抛出异常</param>
        /// <returns></returns>
        public static async Task<T?> DequeueMsgPackObjAsync<T>(this RedisClient redisClient, string key, bool throwErr = false) where T : class, new() {
            return await redisClient.DequeueBytesAsync(key).DeserializeMsgPackAsync<T>(throwErr);
        }
        #endregion
        #region 队列 Stack 先进后出
        /// <summary>
        /// 入栈，<see href="https://redis.io/commands/lpush"/>
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="redisClient"></param>
        /// <param name="key"></param>
        /// <param name="messagePackObj"></param>
        /// <param name="seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次入队没有指定超时时间，之后的入队操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次入队的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的入队操作来计算的。
        /// </param>
        /// <returns></returns>
        public static async Task<RedisClient> PushAsync<T>(this RedisClient redisClient, string key, T messagePackObj, int seconds = 315360000) where T : class, new() {
            return await redisClient.PushAsync(key, SerializeMsgPack(messagePackObj), seconds);
        }
        /// <summary>
        /// 出栈，<see href="https://redis.io/commands/rpop"/>，没有队列，或者队列为空，返回 null
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="redisClient"></param>
        /// <param name="key"></param>
        /// <param name="throwErr">反系列化时发生错误是否抛出异常</param>
        /// <returns></returns>
        public static async Task<T?> PopMsgPackObjAsync<T>(this RedisClient redisClient, string key, bool throwErr = false) where T : class, new() {
            return await redisClient.PopBytesAsync(key).DeserializeMsgPackAsync<T>(throwErr);
        }
        #endregion
        #region Hash
        /// <summary>
        /// 设置Hash表（key）字段（field）的值，<see href="https://redis.io/commands/hset"/>
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="redisClient"></param>
        /// <param name="key"></param>
        /// <param name="field"></param>
        /// <param name="messagePackObj"></param>
        /// <param name="seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次设置没有指定超时时间，之后的设置操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次设置的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的设置操作来计算的。
        /// </param>
        /// <returns></returns>
        public static async Task<RedisClient> HashSetAsync<T>(this RedisClient redisClient, string key, string field, T messagePackObj, int seconds = 315360000) where T : class, new() {
            return await redisClient.HashSetAsync(key, field, SerializeMsgPack(messagePackObj), seconds);
        }
        /// <summary>
        /// 读取Hash表（key）字段（field）的值，<see href="https://redis.io/commands/hget"/>
        /// </summary>
        /// <typeparam name="T">建议使用：<see cref="MessagePackObjectAttribute"/></typeparam>
        /// <param name="redisClient"></param>
        /// <param name="key"></param>
        /// <param name="field"></param>
        /// <param name="throwErr">反系列化时发生错误是否抛出异常</param>
        /// <returns></returns>
        public static async Task<T?> HashGetMsgPackObjAsync<T>(this RedisClient redisClient, string key, string field, bool throwErr = false) where T : class, new() {
            return await redisClient.HashGetBytesAsync(key, field).DeserializeMsgPackAsync<T>(throwErr);
        }
        #endregion
    }
}
